/**
 *  TextMash - simple IDE for Clojure
 *  
 *  Copyright (C) 2010 Aleksander Naszko
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package pl.olek.textmash.workspace;

import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEdit;

import pl.olek.textmash.event.EventDispatcher;
import pl.olek.textmash.matching.OccurrencesMatcher;

/**
 * 
 * @author anaszko
 *
 */
public class CompoundUndoManager extends UndoManager {

	/**
	 * 
	 */
	private static final long serialVersionUID = 8151142681614928985L;

	static class BufferedCompoundEdit extends CompoundEdit {
		/**
		 * 
		 */
		private static final long serialVersionUID = -9190833162341380586L;

		@Override
		public boolean isInProgress() {
			return false;
		}
	}

	CompoundEdit edit;
	long lastAdd;
	boolean foreverMode;
	Workspace workspace;
	boolean enabled;
	UndoableEdit savePoint;

	public CompoundUndoManager(Workspace workspace) {
		setLimit(1024);
		setEnabled(true);
		this.workspace = workspace;
	}

	public void makeAsFresh() {
		try{
			flush();
		savePoint = editToBeUndone();
	} finally {
		computeFreshness();
	}
	}
	
	public static String hash(Object obj) {
		if (obj == null) {
			return "null";
		}
		return "" + obj.hashCode();
	}
	
	public void computeFreshness(){
		if (!isFresh()) {
			EventDispatcher.getInstance().fire(workspace,
					EventDispatcher.WORKSPACE_IS_DIRTY);
		} else {
			EventDispatcher.getInstance().fire(workspace,
					EventDispatcher.WORKSPACE_SAVED);
		}
	}

	public boolean isFresh() {
		
		boolean toUndo = canUndoOrRedo();
		
		Object toEdit = edit;
		if (toEdit == null) {
			toEdit = editToBeUndone();
		}
		
//		System.out.println("GOT savePoint=" + hash(savePoint) + 
//				", editing=" + hash(edit) + ", toEdit="
//				+ hash(toEdit) + " " + toUndo);
		
		if (!toUndo || toEdit == savePoint) {

			return true;
		}
		
		
//		if (!toUndo && savePoint == null) {
//			return true;
//		}
//		
//		if (toUndo && savePoint == null) {
//			return true;
//		}		
//		if (!toUndo && toEdit == null && savePoint == null) {
//			return true;
//		}		
		
		return false;
		
//		return (!canUndo() && (savePoint != lastEdit()))
//		 || (savePoint != null && savePoint == lastEdit())
//		 || (!canUndo() && savePoint == null && lastEdit() == null);
	}

	public void setEnabled(boolean e) {
		enabled = e;
	}

	public boolean isEnabled() {
		return enabled;
	}

	public synchronized void setManualEdit(boolean state) {
		this.foreverMode = state;
		flush();
	}

	private synchronized void flush() {
		if (edit != null) {
			// System.out.println("Flish");
			edit.end();
			super.addEdit(edit);
			edit = null;
		}
	}

	@Override
	public synchronized boolean addEdit(UndoableEdit anEdit) {
		try {
		if (!isEnabled()) {
			return false;
		}
		if (!foreverMode) {
			long now = System.currentTimeMillis();
			if (lastAdd + 400 < now) {
				flush();
			}
			lastAdd = now;
			if (edit == null) {
				edit = new BufferedCompoundEdit();
			}
			edit.addEdit(anEdit);
			return false;
		} else {
			if (edit == null) {
				edit = new CompoundEdit();
			}
			edit.addEdit(anEdit);
		}
		return true;
		} finally {
			computeFreshness();
		}
	}

	@Override
	public synchronized boolean canRedo() {
		if (!isEnabled()) {
			return false;
		}
		return super.canRedo() || (edit != null && edit.canRedo());
	}

	@Override
	public synchronized boolean canUndo() {
		if (!isEnabled()) {
			return false;
		}
		return super.canUndo() || (edit != null && edit.canUndo());
	}

	@Override
	public synchronized boolean canUndoOrRedo() {
		if (!isEnabled()) {
			return false;
		}
		return super.canUndoOrRedo()
				|| (edit != null && (edit.canRedo() || edit.canUndo()));
	}

	@Override
	public synchronized void discardAllEdits() {
		try {
		flush();
		super.discardAllEdits();
		//savePoint = null;
		} finally {
			computeFreshness();
		}
	}

	@Override
	public synchronized void redo() throws CannotRedoException {
		try {
			OccurrencesMatcher.getInstance().refresh();
		flush();
		super.redo();
		} finally {
			computeFreshness();
		}
	}

	@Override
	public synchronized void undo() throws CannotUndoException {
		try {
			OccurrencesMatcher.getInstance().refresh();
			flush();
			super.undo();
		} finally {
			computeFreshness();
		}
	}

}
